Skip to content

fix(themes): handle CC >=2.1.92 assembly format (fixes objArrMatch + obj prefix bugs)#701

Open
zeshuochen wants to merge 5 commits intoPiebald-AI:mainfrom
zeshuochen:fix/themes-patch-new-assembly-format
Open

fix(themes): handle CC >=2.1.92 assembly format (fixes objArrMatch + obj prefix bugs)#701
zeshuochen wants to merge 5 commits intoPiebald-AI:mainfrom
zeshuochen:fix/themes-patch-new-assembly-format

Conversation

@zeshuochen
Copy link
Copy Markdown

@zeshuochen zeshuochen commented Apr 24, 2026

Problem

Fixes #665.

The themes patch fails on Claude Code >=2.1.92 with two bugs. PR #671 fixes Bug 2 but not Bug 1.


Bug 1 — objArr only patches the auto-option variable (not fixed by #671)

In CC >=2.1.92, theme options are no longer a single flat array. Only the "auto" option lives in one variable (e.g. HH=[{label:\"Auto (match terminal)\",value:\"auto\"}]). The remaining built-in themes are individual variables spread in a React-memo cache guard:

e=[...HH,DH,YH,PH,t,o,_H,...U.map(kB1),...FH]

The old code replaced only HH, so DH,YH,… stayed in the spread — resulting in tweakcc themes plus all built-in themes showing in the selector.

Bug 2 — obj replacement emits return{…} at module level (also fixed by #671)

The old code always prefixed the name-mapping object with return. In the new format the mapping is a module-level variable assignment (eB1={auto:\"Auto…\",dark:\"Dark mode\",…}), so replacing it with return{…} produces a syntax error.


Fix

src/patches/themes.ts only:

  1. Assembly-prefix detection (Bug 1): when objArr has exactly 1 item (just "auto"), look for the assembly expression [...autoVar,t1,t2,…,...X.map(. Set objArr startIndex/endIndex to span only the prefix [...HH,DH,…,_H, (everything before the custom-themes spread). writeThemes emits the tweakcc theme array without the closing ] so the existing suffix ...U.map(kB1),...FH] completes the expression — CC's own custom-theme and new-theme-creation entries are preserved.

  2. Prefix extraction (Bug 2): extract the original prefix of the obj match (return or VARNAME=) and use it in the replacement instead of always hard-coding return. (Same approach as CC 2.1.97 compatibility + new patches #671.)

Testing

  • Reproduced both bugs against the v2.1.119 binary (via native-claudejs-orig.js).
  • Verified the patched output: eB1={"winter":"Winter"} (correct assignment), [{"label":"Winter","value":"winter"},...U.map(kB1),...FH] (correct array, built-in themes removed, CC custom themes preserved).
  • All 221 existing tests pass (npx vitest run).
  • TypeScript type-checks clean (tsc --noEmit).
  • Old format (single flat array) still works — the assembly-prefix path is only taken when exactly 1 item is found in objArr.

Summary by CodeRabbit

  • Bug Fixes
    • Improved theme detection to handle newer encoding formats and single-item "auto" assemblies for more reliable theme updates.
    • Preserved original assignment/return prefixes to avoid generating invalid syntax when inserting theme mappings.
    • Safer emission of options arrays: emits either a full array or an open fragment so replacements integrate cleanly with surrounding generated output.

In CC >=2.1.92 the theme options are no longer a single flat array
[{label:"Dark mode",value:"dark"},…]. Instead only the "auto" option
lives in one variable (e.g. HH=[{label:"Auto…",value:"auto"}]) and
the remaining built-in themes are individual variables (DH, YH, PH…)
that are spread together in a React-memo assembly expression:

  e=[...HH,DH,YH,PH,t,o,_H,...U.map(kB1),...FH]

Two bugs result from the old code on the new format:

1. objArr replacement only swapped HH (the auto option), leaving all
   built-in theme variables (DH,YH,…) in the spread, so the selector
   showed tweakcc themes *plus* all built-in themes.

2. obj replacement always emitted `return{…}` as the prefix, turning
   the module-level variable assignment `eB1={…}` into an invalid
   return statement at the top level.

Fix:
- Detect new-assembly format when objArr has exactly 1 item and a
  matching assembly spread is found. In that case objArr.startIndex
  and endIndex span the prefix `[...HH,DH,…,themeN,` (everything up
  to the custom-themes spread `...U.map(…)`). writeThemes emits the
  tweakcc theme array without a closing `]` so the original suffix
  `...U.map(kB1),...FH]` completes the expression — preserving CC's
  own custom-theme and new-theme-creation entries.

- Extract and preserve the original obj prefix (`return` or `VAR=`)
  so the replacement uses the correct syntax for both old and new CC.

Closes Piebald-AI#665
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 46b986ab-aeaf-46fe-a108-011bf9fbd037

📥 Commits

Reviewing files that changed from the base of the PR and between 4a76288 and 68fcfd7.

📒 Files selected for processing (1)
  • src/patches/themes.ts

📝 Walkthrough

Walkthrough

getThemesLocation and writeThemes were updated to handle CC ≥2.1.92 assembly encodings: objArr can be an assembly-prefix span (isAssemblyPrefix) and obj now preserves the original prefix token; writeThemes uses that prefix and emits either a full JSON array or an open-array fragment when splicing into assembly-mode prefixes.

Changes

Theme patching logic

Layer / File(s) Summary
Type / Return shape
src/patches/themes.ts
Introduce ThemesLocation type; getThemesLocation returns `ThemesLocation
Options-array detection
src/patches/themes.ts
For CC ≥2.1.92, detect single-item ["auto"] options arrays, extract the holding variable name, locate surrounding [...] spread assembly, and set objArr.startIndex/endIndex to the assembly-prefix range with isAssemblyPrefix: true.
Mapping-object prefix capture
src/patches/themes.ts
Adjust regex to capture the original prefix (return or VARNAME=), store as obj.prefix, and return enriched obj and objArr locations.
Write/emit changes
src/patches/themes.ts
writeThemes now prefixes the generated mapping with locations.obj.prefix (not hardcoded return). When objArr.isAssemblyPrefix is true, it emits a truncated JSON fragment (open array item + trailing comma) to splice into the assembly; otherwise it emits the full JSON array.
Package manifest
package.json
Package manifest updated (manifest touched in this diff).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • georpar
  • bl-ue

Poem

🐇
I nibbled through tokens late at night,
Kept the prefix snug and held the splice tight,
Left one bracket open, neat and sly,
So themes assemble and never cry,
Hooray — the patch hops just right!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: fixing CC >=2.1.92 assembly format issues with two specific bugs (objArrMatch and obj prefix), which directly match the PR objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 6/8 reviews remaining, refill in 13 minutes and 27 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/patches/themes.ts (2)

122-132: objPrefix via indexOf('{') is safe for current themes but brittle.

objMatch[0].slice(0, objMatch[0].indexOf('{')) relies on the first { being the object-literal opener. Because objPat only accepts values starting with Auto |Dark|Light|Monochrome followed by [^"]*, a theme name containing { would split the prefix incorrectly. No current CC theme does this, so it's a latent concern rather than a bug — but using the capture group of the prefix alternation would be more robust:

♻️ Use an explicit capture for the prefix
   const objPat =
-    /(?:return|[$\w]+=)\{(?:"?(?:[$\w-]+)"?:"(?:Auto |Dark|Light|Monochrome)[^"]*",?)+\}/;
+    /(return|[$\w]+=)\{(?:"?(?:[$\w-]+)"?:"(?:Auto |Dark|Light|Monochrome)[^"]*",?)+\}/;
   const objMatch = oldFile.match(objPat);

   if (!objMatch || objMatch.index == undefined) {
     console.error('patch: themes: failed to find objMatch');
     return null;
   }

-  // Preserve the original prefix (either "return" or "VARNAME=")
-  const objPrefix = objMatch[0].slice(0, objMatch[0].indexOf('{'));
+  // Preserve the original prefix (either "return" or "VARNAME=")
+  const objPrefix = objMatch[1];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/patches/themes.ts` around lines 122 - 132, The current extraction of
objPrefix uses objMatch[0].slice(0, objMatch[0].indexOf('{')), which can break
if a theme value contains a '{'; update the regex objPat to capture the prefix
(the alternation that yields "return" or the variable assignment) as a dedicated
capture group and then set objPrefix from that capture (e.g., use objMatch[1]
instead of slicing), ensuring you still validate objMatch exists before reading
the capture; keep references to objPat, objMatch and objPrefix when making the
change.

92-92: objArrItemCount regex counts both value: and "value": occurrences — confirm no collisions inside label text.

(objArrMatch[0].match(/"?value"?:/g) || []).length counts item entries assuming value: appears once per item. That holds for the known CC emissions ("Dark mode", "Light mode", "Auto (match terminal)", "Monochrome"), but any future label containing the literal substring value: would inflate the count and skip the assembly-prefix branch. A stricter count like \{"?label"?:/g (matching the object-opening prefix) would avoid that class of false positive with no extra cost.

-  const objArrItemCount = (objArrMatch[0].match(/"?value"?:/g) || []).length;
+  const objArrItemCount = (objArrMatch[0].match(/\{"?label"?:/g) || []).length;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/patches/themes.ts` at line 92, The current count in objArrItemCount uses
a permissive pattern that matches both value: and "value": and can be inflated
by label text containing that substring; update the counting logic that computes
objArrItemCount from objArrMatch[0] to detect actual object entries by matching
the object-opening label key instead (i.e., count occurrences of the object
prefix for label rather than value), for example by using a pattern that looks
for '{"label":' or unquoted label keys, and keep the rest of the branch logic
(the assembly-prefix decision) unchanged so it reliably reflects the number of
items.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/patches/themes.ts`:
- Around line 93-117: When objArrItemCount === 1 the code currently falls back
silently if varNameMatch or assemblyMatch fails, which can reintroduce Bug 1;
update the block handling the single-item case so that if varNameMatch is falsy
or assemblyMatch is falsy you log a clear diagnostic (including
autoVarName/assemblyPat context) and return null instead of leaving
objArrLocation pointing at the single-item array. Specifically, in the branch
that computes varNameMatch/autoVarName and then assemblyMatch (using assemblyPat
and oldFile), add explicit checks immediately after each match and bail out with
a logged error/diagnostic and a null return when a required match is missing.
Ensure you reference objArrItemCount, varNameMatch, assemblyMatch, autoVarName,
assemblyPat and objArrLocation when locating the code to change.

---

Nitpick comments:
In `@src/patches/themes.ts`:
- Around line 122-132: The current extraction of objPrefix uses
objMatch[0].slice(0, objMatch[0].indexOf('{')), which can break if a theme value
contains a '{'; update the regex objPat to capture the prefix (the alternation
that yields "return" or the variable assignment) as a dedicated capture group
and then set objPrefix from that capture (e.g., use objMatch[1] instead of
slicing), ensuring you still validate objMatch exists before reading the
capture; keep references to objPat, objMatch and objPrefix when making the
change.
- Line 92: The current count in objArrItemCount uses a permissive pattern that
matches both value: and "value": and can be inflated by label text containing
that substring; update the counting logic that computes objArrItemCount from
objArrMatch[0] to detect actual object entries by matching the object-opening
label key instead (i.e., count occurrences of the object prefix for label rather
than value), for example by using a pattern that looks for '{"label":' or
unquoted label keys, and keep the rest of the branch logic (the assembly-prefix
decision) unchanged so it reliably reflects the number of items.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 80c4073a-c241-48d0-a7b2-98ba8099b032

📥 Commits

Reviewing files that changed from the base of the PR and between b068b20 and fb60812.

📒 Files selected for processing (1)
  • src/patches/themes.ts

Comment thread src/patches/themes.ts
- Use capture group in objPat to extract prefix instead of indexOf('{')
- Count items with /\{"?label"?:/g to avoid false positives from label text
- Return null with diagnostics when varNameMatch/assemblyMatch fail in
  single-item case, preventing silent fallback to incorrect HH-only patch
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/patches/themes.ts (2)

107-110: Minor: {2,} may be slightly over-constrained.

The assembly pattern requires ≥2 intermediate theme identifiers between ...autoVar and the next spread. Current CC has 6+ built-ins (DH,YH,PH,t,o,_H), so this works and is unlikely to overmatch. If a future CC release ever reduces built-ins to 1 (e.g. [...HH,DH,...U.map(...)]), detection will fail-safe to null with the clear error you added — not a regression, just something to keep in mind. Relaxing to {1,} would be more forgiving at the (small) cost of stricter reliance on the trailing ,\.\.\. anchor; either is defensible.

The ReDoS warning from the static analyzer on this new RegExp is a false positive: autoVarName is bounded by [A-Za-z_$][\w$]* and the only metachar that could appear ($) is explicitly escaped, so the constructed pattern is always safe.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/patches/themes.ts` around lines 107 - 110, The assemblyPat RegExp in
src/patches/themes.ts is too strict by requiring at least two intermediate
identifiers between the ...autoVar spread and the next spread; update the
pattern used to build assemblyPat (the RegExp constructed around autoVarName) to
allow one or more intermediates (change the quantifier from {2,} to {1,}) so
assemblies with a single built-in identifier are matched; leave the escaped
metacharacters and surrounding anchors intact (no other logic changes) to
preserve safety against ReDoS.

196-217: Add test coverage for the assembly prefix path.

The splicing logic traces correctly: the replaced span ends at the comma before ...U.map(...), and the emitted fragment [{...themes...}, joins cleanly with the untouched suffix, yielding the expected output. The legacy path (full-array replacement) is preserved when isAssemblyPrefix is falsy.

However, themes.ts has no corresponding test file, unlike other patch files (systemPrompts.test.ts, voiceMode.test.ts, etc.). Since both the regex detection and the assembly splicing are critical to correct operation, a test fixture covering both the assembly path (with isAssemblyPrefix: true) and the legacy path would harden this against future Claude Code format changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/patches/themes.ts` around lines 196 - 217, Add unit tests for
src/patches/themes.ts to cover both assembly-prefix and legacy replacement
paths: create fixtures exercising the code that computes objArr (use themes.map
-> themeItems), toggling locations.objArr.isAssemblyPrefix true and false, and
assert the produced newFile fragments join correctly with the untouched suffix
(verify the emitted open-array case ends with a comma and the full-array case
contains the closing bracket). Target the functions/logic around
locations.objArr, objArr, and the showDiff call so the regex detection and
splicing behavior are validated, mirroring existing test patterns used in
systemPrompts.test.ts and voiceMode.test.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/patches/themes.ts`:
- Around line 107-110: The assemblyPat RegExp in src/patches/themes.ts is too
strict by requiring at least two intermediate identifiers between the ...autoVar
spread and the next spread; update the pattern used to build assemblyPat (the
RegExp constructed around autoVarName) to allow one or more intermediates
(change the quantifier from {2,} to {1,}) so assemblies with a single built-in
identifier are matched; leave the escaped metacharacters and surrounding anchors
intact (no other logic changes) to preserve safety against ReDoS.
- Around line 196-217: Add unit tests for src/patches/themes.ts to cover both
assembly-prefix and legacy replacement paths: create fixtures exercising the
code that computes objArr (use themes.map -> themeItems), toggling
locations.objArr.isAssemblyPrefix true and false, and assert the produced
newFile fragments join correctly with the untouched suffix (verify the emitted
open-array case ends with a comma and the full-array case contains the closing
bracket). Target the functions/logic around locations.objArr, objArr, and the
showDiff call so the regex detection and splicing behavior are validated,
mirroring existing test patterns used in systemPrompts.test.ts and
voiceMode.test.ts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3ad2bb94-7afc-4477-af0b-33ae595057de

📥 Commits

Reviewing files that changed from the base of the PR and between e2f850b and a84edfc.

📒 Files selected for processing (1)
  • src/patches/themes.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Themes patch fails on Claude Code v2.1.92 — objArrMatch not found

1 participant